#!/usr/bin/perl -w -I/home/furumi/src/mp4check

use Tabler;
use FileHandle;
use Peekable;
use Math::BigInt;

use strict;

sub bigendian_to_num {
    my $v = shift;
    my $val = 0;
    if ( length($v) > 4 ) {
	my $val = Math::BigInt->new(0);
    }

    while ( length($v) > 0 ) {
	$val = ord(substr($v, 0, 1)) + $val * 256;
	$v = substr($v, 1);
    }
    return $val;
}

sub process_box_header {
    my $fh = shift;
    my $level = shift;

    my $no_print = $_[1];
    
    my $len;
    my $read_len = 0;
    my $type;
    my $pos;
    my $d;

    $pos = $fh->tell;
    $fh->peek($d, 8) == 8 || ( warn("stream too short"), return 0);
    $len = bigendian_to_num(substr($d, 0, 4));
    $type = substr($d, 4, 4);
    $fh->skip(8) == 8 || return 0;
    $read_len += 8;
    if ( $len == 1 ) {
	$fh->peek($d, 8) == 8 || return 0;
	$len = bigendian_to_num(substr($d, 8, 8));
	$fh->skip(8) == 8 || return 0;
	$read_len += 8;
    }
    if ( $len > 0 && $len < $read_len ) {
	return 0;
    }
    if ( !$no_print ) {
	print sprintf("%012x", $pos), " ";
	print " " x (2*$level), "ATOM (";
	print sprintf("%08x", $len), ") '";
	print substr($d, 4, 4), "' <";
	if ( $len >= 8 ) {
	    print sprintf("%012x", $pos+$len);
	} else {
	    print "box extends till end of stream";
	}
	print sprintf(">\n");
    }
    $_[0] = $len - $read_len;
    return 1;
}

sub skip_box {
    my $fh = shift;
    my $level = shift;
    my $d;
    my $len = 0;
    my $objects;
    my $locallen;

    process_box_header($fh, $level, $len) || die("too short file\n");
    $fh->skip($len);
    
    $_[0] -= $len;
}


sub parse_dump {
    my $fh = shift;
    my $level = shift;
    my $len = shift;    
    my $size;
    my $d;
    my $i;

    while ( $len > 0 ) {
	$size = $len;
	$size = 16 if $size > 16;

	$fh->peek($d, $size) == $size || die("too short file");
	
	print sprintf("%012x", $fh->tell);
	print " " x ( 1 + $level * 2 ), "( ";
	for $i ( 0 .. $size-1 ) {
	    print sprintf("%02x ", ord(substr($d, $i, 1)));
	}
	print "   " x ( 16 - $size );
	print ")\n";

	$fh->skip($size) == $size || die("too short file");
	$len -= $size;
    }

}

sub parse_ftyp_box {
    my $fh = shift;
    my $depth = shift;
    my $pos;
    my $d;
    my $size;
    my $len;
    my $i;
    my $val;
    my $tabler = Tabler->new(1);

    process_box_header($fh, 0, $len) || die("too short file");
    $size = $len;
    if ( $len < 8 ) {
	die("parse_ftyp_box: too small box, len=" . $len);
    }
    if ( $len % 4 != 0 ) {
	die("parse_ftyp_box: opaque len=" . $len . ", length must be multi of 4");
    }
    $pos = $fh->tell;
    $fh->peek($d, 8) == 8 || die("too short file");
    $val = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Major brand: ", "'" .  substr($d, 0, 4) . "' (" . sprintf("%08x", $val) . ")");
    $pos += 4;
    $val = bigendian_to_num(substr($d, 4, 4));
    $tabler->add(sprintf("%012x", $pos), "Minor version: ", $val . " (" . sprintf("%08x", $val) . ")");
    $fh->skip(8) == 8 || die("too short file");
    $len -= 8;
    $pos += 4;

    $i = 0;
    while ( $len > 0 ) {
	$fh->peek($d, 4) == 4 || die("too short file");
	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler->add(sprintf("%012x", $pos), "Compatible brands[" . $i . "]: ", "'" . substr($d, 0, 4) . "' (" . sprintf("%08x", $val) . ")");
	$fh->skip(4) == 4 || die("too short file");
	$len -= 4;
	$pos += 4;
	$i++;
    }

    $tabler->print;
}

sub parse_tkhd_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $type;
    my $late_flag;
    my $tabler = Tabler->new($depth+1);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 84 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");
    $late_flag = 0;
    if ( $val & 0x1 ) {
	$tabler->add("", "", "= TRACK_ENABLED");
	$late_flag = 1;
    }
    if ( $val & 0x2 ) {
	$tabler->add("", "", ($late_flag ? "  |" : "=") . " TRACK_IS_MOVIE");
	$late_flag = 1;
    }
    if ( $val & 0x4 ) {
	$tabler->add("", "", ($late_flag ? "  |" : "=") . " TRACK_IS_PREVIEW");
	$late_flag = 1;
    }

    if ( $version > 1 ) {
	warn("too large version, $version");
	goto error;
    }
    if ( $version == 1 ) {
	if ( $len < 96 ) {
	    warn("too short size, $len for version $version");
	    goto error;
	}
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    if ( $version == 1 ) {
	$fh->peek($d, 32) == 32 || die("too short file");
	
	$val = bigendian_to_num(substr($d, 0, 8));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%016x", $val) . ")");
	$pos += 8;

	$val = bigendian_to_num(substr($d, 8, 8));
	$tabler->add(sprintf("%012x", $pos), "Modification time: ", " (" . sprintf("%016x", $val) . ")");
	$pos += 8;

	$val = bigendian_to_num(substr($d, 16, 4));
	$tabler->add(sprintf("%012x", $pos), "Track ID: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 8;

	$duration = bigendian_to_num(substr($d, 24, 8));
	
	if ( @{$inforef}{"mp4_duration_scale"} ) {
	    my $tabler_child = Tabler->new($depth+2);
	    
	    $val = $duration / @{$inforef}{"mp4_duration_scale"};
	    $tabler_child->add(" " x 12, "Duration time: ", sprintf("%0.3f", $val));
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%016x", $duration) . ")", $tabler_child);
	} else {
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%016x", $duration) . ")");
	}
	$pos += 8;
	
	$fh->skip(32) == 32 || die("too short file");
	$len -= 32;	
    } else {
	# version is zero
	
	$fh->peek($d, 20) == 20 || die("too short file");
	
	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 4, 4));
	$tabler->add(sprintf("%012x", $pos), "Modification time: ", " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 8, 4));
	$tabler->add(sprintf("%012x", $pos), "Track ID: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 8;
	
	$duration = bigendian_to_num(substr($d, 16, 4));
	
	if ( @{$inforef}{"mp4_duration_scale"} ) {
	    my $tabler_child = Tabler->new($depth+2);
	    
	    $val = $duration / @{$inforef}{"mp4_duration_scale"};
	    $tabler_child->add(" " x 12, "Duration time: ", sprintf("%0.3f", $val));
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%08x", $duration) . ")", $tabler_child);
	} else {
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%08x", $duration) . ")");
	}
	$pos += 4;
	
	$fh->skip(20) == 20 || die("too short file");
	$len -= 20;
    }
    
    $pos = $fh->tell;
    $fh->peek($d, 64) == 64 || die("too short file");
    $fh->skip($len) == $len || die("too short file");
    $tabler->print;
    
    return 1;
}

sub parse_tkhd_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $type;


    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 84 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));

    $pos += 1;

    if ( $version > 1 ) {
	warn("too large version, $version");
	goto error;
    }
    if ( $version == 1 ) {
	if ( $len < 96 ) {
	    warn("too short size, $len for version $version");
	    goto error;
	}
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    if ( $version == 1 ) {
	$fh->peek($d, 32) == 32 || die("too short file");
	if ( 0 ) {	
	    $val = bigendian_to_num(substr($d, 0, 8));
	    $val = bigendian_to_num(substr($d, 8, 8));
	}

	$val = bigendian_to_num(substr($d, 16, 4));
	$streamref->{track_id} = $val;

	$duration = bigendian_to_num(substr($d, 24, 8));
	$streamref->{mp4_duration_count_tkhd} = $duration;
	
	$fh->skip(32) == 32 || die("too short file");
	$len -= 32;	
    } else {
	# version is zero
	
	$fh->peek($d, 20) == 20 || die("too short file");
	if ( 0 ) {
	    $val = bigendian_to_num(substr($d, 0, 4));
	    $val = bigendian_to_num(substr($d, 4, 4));
	}

	$val = bigendian_to_num(substr($d, 8, 4));
	$streamref->{track_id} = $val;
	
	$duration = bigendian_to_num(substr($d, 16, 4));
	$streamref->{mp4_duration_count_tkhd} = $duration;
	
	$fh->skip(20) == 20 || die("too short file");
	$len -= 20;
    }
    
    $pos = $fh->tell;
    $fh->peek($d, 64) == 64 || die("too short file");
    $fh->skip($len) == $len || die("too short file");
    
    $streamref->{mp4_had_tkhd_box} = 1;    
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_mdhd_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $type;


    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 24 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));

    if ( $version > 1 ) {
	warn("too large version, $version");
	goto error;
    }
    if ( $version == 1 ) {
	if ( $len < 36 ) {
	    warn("too short size, $len for version $version");
	    goto error;
	}
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    if ( $version == 1 ) {
	$fh->peek($d, 28) == 28 || die("too short file");
	if ( 0 ) {	
	    $val = bigendian_to_num(substr($d, 0, 8));
	    $val = bigendian_to_num(substr($d, 8, 8));
	}

	$val = bigendian_to_num(substr($d, 16, 4));
	$streamref->{mp4_duration_scale_mdhd} = $val;

	$duration = bigendian_to_num(substr($d, 20, 8));
	$streamref->{mp4_duration_count_mdhd} = $duration;
	
	$fh->skip(28) == 28 || die("too short file");
	$len -= 28;	
    } else {
	# version is zero
	
	$fh->peek($d, 16) == 16 || die("too short file");
	if ( 0 ) {
	    $val = bigendian_to_num(substr($d, 0, 4));
	    $val = bigendian_to_num(substr($d, 4, 4));
	}

	$val = bigendian_to_num(substr($d, 8, 4));
	$streamref->{mp4_duration_scale_mdhd} = $val;
	
	$duration = bigendian_to_num(substr($d, 12, 4));
	$streamref->{mp4_duration_count_mdhd} = $duration;
	
	$fh->skip(16) == 16 || die("too short file");
	$len -= 16;
    }
    
    $pos = $fh->tell;
    $fh->peek($d, 4) == 4 || die("too short file");
    $val = bigendian_to_num(substr($d, 0, 2));
  lang_parse:
    if ( $val ) {
	my $chara;
	my $lang = "";
	
	$chara = ($val>>10) & 0x1f;
	if ( $chara == 0x1f ) { last lang_parse; }
	$lang .= chr($chara+0x60);
	
	$chara = ($val>>5) & 0x1f;
	if ( $chara == 0x1f ) { last lang_parse; }
	$lang .= chr($chara+0x60);
	
	$chara = $val & 0x1f;
	if ( $chara == 0x1f ) { last lang_parse; }
	$lang .= chr($chara+0x60);
	
	$streamref->{lang} = $lang;
    }
    
    $fh->skip($len) == $len || die("too short file");
    
    $streamref->{mp4_had_tkhd_box} = 1;    
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_mdhd_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $my_lang = "";
    my $type;
    my $tabler = Tabler->new($depth+1);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 24 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 1 ) {
	warn("too large version, $version");
	goto error;
    }
    if ( $version == 1 ) {
	if ( $len < 36 ) {
	    warn("too short size, $len for version $version");
	    goto error;
	}
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    if ( $version == 1 ) {
	$fh->peek($d, 28) == 28 || die("too short file");

	$val = bigendian_to_num(substr($d, 0, 8));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%016x", $val) . ")");
	$pos += 8;

	$val = bigendian_to_num(substr($d, 8, 8));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%016x", $val) . ")");
	$pos += 8;

	$val = bigendian_to_num(substr($d, 16, 4));
	$tabler->add(sprintf("%012x", $pos), "Scale: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$duration = bigendian_to_num(substr($d, 20, 8));
	if ( $val ) {
	    my $tabler_child = Tabler->new($depth+2);
	    
	    $val = $duration / $val;
	    $tabler_child->add(" " x 12, "Duration time: ", sprintf("%0.3f", $val));
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $val . " (" . sprintf("%016x", $val) . ")", $tabler_child);
	} else {
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $val . " (" . sprintf("%016x", $val) . ")");
	}
	$pos += 8;
	
	$fh->skip(28) == 28 || die("too short file");
	$len -= 28;	
    } else {
	# version is zero
	
	$fh->peek($d, 16) == 16 || die("too short file");

	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 4, 4));
	$tabler->add(sprintf("%012x", $pos), "Modification time: ", " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 8, 4));
	$tabler->add(sprintf("%012x", $pos), "Scale: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
	
	$duration = bigendian_to_num(substr($d, 12, 4));
	if ( $val ) {
	    my $tabler_child = Tabler->new($depth+2);
	    
	    $val = $duration / $val;
	    $tabler_child->add(" " x 12, "Duration time: ", sprintf("%0.3f", $val));
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%08x", $duration) . ")", $tabler_child);
	} else {
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%08x", $duration) . ")");
	}
	
	$fh->skip(16) == 16 || die("too short file");
	$len -= 16;
    }
    
    $pos = $fh->tell;
    $fh->peek($d, 4) == 4 || die("too short file");
    undef $my_lang;
    $val = bigendian_to_num(substr($d, 0, 2));
  lang_parse:
    if ( $val != 0 ) {
	my $chara;
	my $lang = "";
	
	$chara = ($val>>10) & 0x1f;
	if ( $chara == 0x1f ) { last lang_parse; }
	$lang .= chr($chara+0x60);

	$chara = ($val>>5) & 0x1f;
	if ( $chara == 0x1f ) { last lang_parse; }
	$lang .= chr($chara+0x60);

	$chara = $val & 0x1f;
	if ( $chara == 0x1f ) { last lang_parse; }
	$lang .= chr($chara+0x60);

	$my_lang = $lang;
    }
    if ( defined $my_lang ) {
	$tabler->add(sprintf("%012x", $pos), "Language: ", "'" . $my_lang . "' (" . sprintf("%04x", $val) . ")");
    } else {
	$tabler->add(sprintf("%012x", $pos), "Language: ", " (" . sprintf("%04x", $val) . ")");
    }

    $fh->skip($len) == $len || die("too short file");
    $tabler->print;
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_hdlr_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $type;


    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 24 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 8) == 8 || die("too short file");
    
    $val = substr($d, 4, 4);
    $streamref->{mp4_handler_type} = $val;
    $streamref->{stream_type} = "unknown";
    $streamref->{stream_type_sub} = $val;
    if ( $val eq "vide" ) {
	$streamref->{stream_type} = "video";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "soun" ) {
	$streamref->{stream_type} = "audio";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "odsm" ) {
	$streamref->{stream_type} = "mp4_object_descriptor";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "crsm" ) {
	$streamref->{stream_type} = "mp4_clock_reference";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "sdsm" ) {
	$streamref->{stream_type} = "mp4_scene_descriptor";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "m7sm" ) {
	$streamref->{stream_type} = "mpeg7";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "ocsm" ) {
	$streamref->{stream_type} = "mp4_object_content_info";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "ipsm" ) {
	$streamref->{stream_type} = "mp4_ipmp";
	$streamref->{stream_type_sub} = "";
    } elsif ( $val eq "mjsm" ) {
	$streamref->{stream_type} = "mpeg-j";
	$streamref->{stream_type_sub} = "";
    }
    
    $fh->skip(20) == 20 || die("too short file");
    $len -= 20;

    if ( $len > 0 ) {
	$fh->peek($d, 1) == 1 || die("too short file");
	$val = bigendian_to_num(substr($d, 0, 1));
	$fh->skip(1) == 1 || die("too short file");
	$len -= 1;

	$fh->peek($d, $len) == $len || die("too short file");
	if ( $val > $len ) {
	    $val = $len;
	}
	$streamref->{mp4_stream_name} = substr($d, 0, $val);
    
	$fh->skip($len) == $len || die("too short file");
    }

    $streamref->{mp4_had_hdlr_box} = 1;    
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_hdlr_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $my_lang = "";
    my $type;
    my $tabler = Tabler->new($depth+1);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 24 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 8) == 8 || die("too short file");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 4, 4));
    $tabler->add(sprintf("%012x", $pos), "Handler type: ", "'" .  substr($d, 4, 4) . "' (" . sprintf("%08x", $val) . ")");
    $pos += 4;
    
    $fh->skip(20) == 20 || die("too short file");
    $len -= 20;
    
    $pos = $fh->tell;

    if ( $len > 0 ) {
	$fh->peek($d, $len) == $len || die("too short file");
	$val = bigendian_to_num(substr($d, 0, 1));
	$tabler->add(sprintf("%012x", $pos), "Name length: ", $val . " (" . sprintf("%02x", $val) . ")");
	$pos += 1;
	$tabler->add(sprintf("%012x", $pos), "Name: ", "'" . substr($d, 1) . "'");
	$fh->skip($len) == $len || die("too short file");
    }
    $tabler->print;
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_esds_box {
    my $fh = shift;
    my $depth = shift;

    my $len;
    my $pos;

    my $d;
    my $version;
    my $val;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);

    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 4 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $tabler->print;

    if ( $len > 0 ) {
	$tabler->add(sprintf("%012x", $pos), "ES Descriptor: ", "");
	$tabler->print;
	parse_dump($fh, $depth+2, $len);
    }
    return 1;
}

sub parse_avcc_box {
    my $fh = shift;
    my $depth = shift;

    my $len;
    my $pos;

    my $i;
    my $d;
    my $my_len;
    my $val;
    my $version;
    my $sps;
    my $pps;
    my $nul;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);

    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 6 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 6) == 6 || die("too short file");

    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Config. version: ", $version . " (" . sprintf("%02x", $version) . ")");
    $pos += 1;

    $val = bigendian_to_num(substr($d, 1, 1));
    $tabler->add(sprintf("%012x", $pos), "AVC profile indication: ", $val . " (" . sprintf("%02x", $val) . ")");
    $pos += 1;

    $val = bigendian_to_num(substr($d, 2, 1));
    $tabler->add(sprintf("%012x", $pos), "Profile compatibility: ", " (" . sprintf("%02x", $val) . ")");
    $pos += 1;

    $val = bigendian_to_num(substr($d, 3, 1));
    $tabler->add(sprintf("%012x", $pos), "AVC level indication: ", $val . " (" . sprintf("%02x", $val) . ")");
    $pos += 1;

    $val = bigendian_to_num(substr($d, 4, 1));
    $nul = $val & 0x03;
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%02x", $val) . ")");
    $tabler->add(" " x 12, "  NAL unit length: ", $nul+1 . " (" . sprintf("%01x", $nul) . ")");
    $pos += 1;

    $val = bigendian_to_num(substr($d, 5, 1));
    $sps = $val & 0x1f;
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%02x", $val) . ")");
    $tabler->add(" " x 12, "  Number of Seq. param. sets: ", $sps . " (" . sprintf("%02x", $sps) . ")");
    $pos += 1;

    $fh->skip(6) == 6 || die("too short file");
    $len -= 6;
    
    $tabler->print;
    
    for ( $i = 0; $i<$sps; $i++ ) {
	$pos = $fh->tell;
	$tabler->add(sprintf("%012x", $pos), "Seq. param. sets[" . $i . "]:");
	$tabler->print;

	if ( $len < 2 ) {
	    warn("too short size, $len");
	    goto error;
	}
	$fh->peek($d, 2) == 2 || die("too short file");

	$my_len = bigendian_to_num(substr($d, 0, 2));
	$tabler_child->add(sprintf("%012x", $pos), "Length: ", $my_len . " (" . sprintf("%04x", $my_len) . ")");	
	$fh->skip(2) == 2 || die("too short file");
	$len -= 2;

	$pos = $fh->tell;
	$tabler_child->add(sprintf("%012x", $pos), "NAL unit: ");
	$tabler_child->print;
	parse_dump($fh, $depth+3, $my_len);
	$len -= $my_len;
    }
	
    $pos = $fh->tell;
    if ( $len < 1 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 1) == 1 || die("too short file");

    $pps = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Number of Pict. param. sets: ", $sps . " (" . sprintf("%02x", $sps) . ")");
    $pos += 1;

    $fh->skip(1) == 1 || die("too short file");
    $len -= 1;
    
    $tabler->print;
    
    for ( $i = 0; $i<$pps; $i++ ) {
	$pos = $fh->tell;
	$tabler->add(sprintf("%012x", $pos), "Pict. param. sets[" . $i . "]:");
	$tabler->print;

	if ( $len < 2 ) {
	    warn("too short size, $len");
	    goto error;
	}
	$fh->peek($d, 2) == 2 || die("too short file");

	$my_len = bigendian_to_num(substr($d, 0, 2));
	$tabler_child->add(sprintf("%012x", $pos), "Length: ", $my_len . " (" . sprintf("%04x", $my_len) . ")");	
	$fh->skip(2) == 2 || die("too short file");
	$len -= 2;

	$pos = $fh->tell;
	$tabler_child->add(sprintf("%012x", $pos), "NAL unit: ");
	$tabler_child->print;
	parse_dump($fh, $depth+3, $my_len);
	$len -= $my_len;
    }
	


    if ( $len > 0 ) {
	parse_dump($fh, $depth+2, $len);
    }
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_stsd_sound_sample_entry {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $len;
    my $pos;

    my $version;
    my $desc_len;
    my $coding_name;
    my $d;
    my $poscur;
    my $val;
    my $tabler = Tabler->new($depth+1);

    $fh->peek($d, 4) == 4 || die("too short file");
    $len = bigendian_to_num(substr($d, 0, 4));
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 8) == 8 || die("too short file");
    $coding_name = substr($d, 4, 4);
    if ( !defined($streamref->{mp4_coding_name}) ) {
	$streamref->{mp4_coding_name} = $coding_name;
    }
    $desc_len = 28;

    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < $desc_len ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, $desc_len) == $desc_len || die("too short file");

    $pos += 6;
    $val = bigendian_to_num(substr($d, 6, 2));
    $tabler->add(sprintf("%012x", $pos), "Data reference index: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $version = bigendian_to_num(substr($d, 8, 2));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%04x", $version) . ")");
    $pos += 2;

    if ( $version >= 1 ) {
	$desc_len = 44;
    }
    if ( $version > 1 ) {
	warn("unsupported version, $version");	
    }
    
    if ( $len < $desc_len ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, $desc_len) == $desc_len || die("too short file");

    $val = bigendian_to_num(substr($d, 10, 2));
    $tabler->add(sprintf("%012x", $pos), "Revision level: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $pos += 4;
    $val = bigendian_to_num(substr($d, 16, 2));
    $tabler->add(sprintf("%012x", $pos), "Number of channels: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 18, 2));
    $tabler->add(sprintf("%012x", $pos), "Sample size: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 20, 2));
    $tabler->add(sprintf("%012x", $pos), "Compression ID: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 22, 2));
    $tabler->add(sprintf("%012x", $pos), "Packet size: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 24, 4));
    $tabler->add(sprintf("%012x", $pos), "Sample rate: ", $val/65536 . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    if ( $version >= 1 ) {
	$val = bigendian_to_num(substr($d, 28, 4));
	$tabler->add(sprintf("%012x", $pos), "Sample per Packet: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
	
	$val = bigendian_to_num(substr($d, 32, 4));
	$tabler->add(sprintf("%012x", $pos), "Bytes per Packet: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
	
	$val = bigendian_to_num(substr($d, 36, 4));
	$tabler->add(sprintf("%012x", $pos), "Bytes per Frame: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
	
	$val = bigendian_to_num(substr($d, 40, 4));
	$tabler->add(sprintf("%012x", $pos), "Bytes per Sample: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
    }

    $tabler->print;
    
    $fh->skip($desc_len) == $desc_len || die("too short file");
    $len -= $desc_len;

    if ( $coding_name eq "mp4a" ) {
	if ( !defined($streamref->{coding}) ) {
	    $streamref->{coding} = "mpeg_4_audio";
	}
    }

    while ( $len > 0 ) {
	my $size;
	my $type;

	if ( $len < 8 ) {
	    last;
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "esds" ) {
	    parse_esds_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "avcC" ) {
	    parse_avcc_box($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    skip_box($fh, $depth+1);
	}

	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }

    if ( $len > 0 ) {
	$pos = $fh->tell;
	$tabler->add(sprintf("%012x", $pos), "Extended data:");
	$tabler->print;
	
	parse_dump($fh, $depth+2, $len);
    }

    return 1;
}

sub parse_stsd_video_sample_entry {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $len;
    my $pos;

    my $version;
    my $coding_name;
    my $d;
    my $poscur;
    my $val;
    my $tabler = Tabler->new($depth+1);

    $fh->peek($d, 4) == 4 || die("too short file");
    $len = bigendian_to_num(substr($d, 0, 4));
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 8) == 8 || die("too short file");
    $coding_name = substr($d, 4, 4);
    if ( !defined($streamref->{mp4_coding_name}) ) {
	$streamref->{mp4_coding_name} = $coding_name;
    }

    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 78 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 78) == 78 || die("too short file");

    $pos += 6;
    $version = bigendian_to_num(substr($d, 6, 2));
    $tabler->add(sprintf("%012x", $pos), "Data reference index: ", $version . " (" . sprintf("%04x", $version) . ")");
    $pos += 2;

    $version = bigendian_to_num(substr($d, 8, 2));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%04x", $version) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 10, 2));
    $tabler->add(sprintf("%012x", $pos), "Revision level: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $pos += 4;
    $val = bigendian_to_num(substr($d, 16, 4));
    $tabler->add(sprintf("%012x", $pos), "Temporal quality: ", $val . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 20, 4));
    $tabler->add(sprintf("%012x", $pos), "Spatial quality: ", $val . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 24, 2));
    $tabler->add(sprintf("%012x", $pos), "Width: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 26, 2));
    $tabler->add(sprintf("%012x", $pos), "Height: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 28, 4));
    $tabler->add(sprintf("%012x", $pos), "Horizontal resolution: ", sprintf("%.3f", $val/65536) . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 32, 4));
    $tabler->add(sprintf("%012x", $pos), "Vertical resolution: ", sprintf("%.3f", $val/65536) . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 36, 4));
    $tabler->add(sprintf("%012x", $pos), "Data size: ", $val . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 40, 2));
    $tabler->add(sprintf("%012x", $pos), "Frame count: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $pos += 32;
    $val = bigendian_to_num(substr($d, 74, 2));
    $tabler->add(sprintf("%012x", $pos), "Depth: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $val = bigendian_to_num(substr($d, 76, 2));
    $tabler->add(sprintf("%012x", $pos), "Color table ID: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $tabler->print;
    
    $fh->skip(78) == 78 || die("too short file");
    $len -= 78;

    if ( $coding_name eq "mp4v" ) {
	if ( !defined($streamref->{coding}) ) {
	    $streamref->{coding} = "mpeg_4_video";
	}
    } elsif ( $coding_name eq "avc1" ) {
	if ( !defined($streamref->{coding}) ) {
	    $streamref->{coding} = "h264";
	}
    }

    while ( $len > 0 ) {
	my $size;
	my $type;

	if ( $len < 8 ) {
	    last;
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "esds" ) {
	    parse_esds_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "avcC" ) {
	    parse_avcc_box($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    skip_box($fh, $depth+1);
	}

	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }

    if ( $len > 0 ) {
	$pos = $fh->tell;
	$tabler->add(sprintf("%012x", $pos), "Extended data:");
	$tabler->print;
	
	parse_dump($fh, $depth+2, $len);
    }

    return 1;
}

sub parse_stsd_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;

    if ( !process_box_header($fh, $depth, $len, 1) ) { warn("too short file"); goto error; }

    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file, len=$len"); goto error; }
    }
    
    $streamref->{mp4_had_stsd_box} ++;
    return 1;

  error:
    return undef;
}


sub parse_stsd_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $entrycount;
    my $i;
    my $type;
    my $tabler = Tabler->new($depth+1);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 4) == 4 || die("too short file");

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%02x", $entrycount) . ")");
    $pos += 4;
    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;

    $tabler->print;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	$pos = $fh->tell;
	$tabler->add(sprintf("%012x", $pos), sprintf("Entry[%d]:", $i));
	$tabler->print;

	if ( $streamref->{mp4_handler_type} eq "vide" ) {
	    if ( !parse_stsd_video_sample_entry($fh, $depth+2, $inforef, $streamref) ) {
		goto error;
	    }
	} elsif ( $streamref->{mp4_handler_type} eq "soun" ) {
	    if ( !parse_stsd_sound_sample_entry($fh, $depth+2, $inforef, $streamref) ) {
		goto error;
	    }
	} else {
	    skip_box($fh, $depth+2);
	}

	$len -= $fh->tell - $pos;
    }



    $fh->skip($len) == $len || die("too short file");


    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_stts_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $sample;
    my $d;
    my $poscur;
    my $val;
    my $entrycount;
    my $i;
    my $samples;
    my $duration;
    my $count;
    my $delta;
    my $paramlist;

    if ( $streamref->{mp4_had_stts_box} ) { warn("stream has stts box of more than 1"); }


    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;

    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $pos += 4;
    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;

    $sample = 0;
    $duration = 0;
    $paramlist = "";
    
    for ( $i=0; $i<$entrycount; $i++ ) {

	if ( $len < 8 ) {
	    warn("too short size, $len");
	    goto error;
	}
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	
	if ( $paramlist ne "" ) { $paramlist .= ";"; }

	$count = bigendian_to_num(substr($d, 0, 4));
	$pos += 4;

	$delta = bigendian_to_num(substr($d, 4, 4));
	$pos += 4;
	
	$sample += $count;
	$duration += $count * $delta;

	$paramlist .= $count;
	$paramlist .= ",";
	$paramlist .= $delta;
	$paramlist .= ",";
	$paramlist .= $sample;
	$paramlist .= ",";
	$paramlist .= $duration;

	if ( $fh->skip(8) != 8 ) { warn("too short file"); goto error; }
	$len -= 8;
    }


    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); goto error; }
    }

    $streamref->{mp4_stts_list} = $paramlist; 
    $streamref->{mp4_stts_sample_count} = $sample;     
    $streamref->{mp4_had_stts_box} ++;
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); goto error; }
    return 0;
}

sub parse_stts_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $sample;
    my $d;
    my $poscur;
    my $val;
    my $entrycount;
    my $i;
    my $samples;
    my $duration;
    my $count;
    my $delta;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 4) == 4 || die("too short file");

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%08x", $entrycount) . ")");
    $pos += 4;
    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;

    $tabler->print;

    $sample = 0;
    $duration = 0;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	$tabler->add(sprintf("%012x", $pos), sprintf("Entry[%d]:", $i));
	$tabler->print;

	if ( $len < 8 ) {
	    warn("too short size, $len");
	    goto error;
	}
	$fh->peek($d, 8) == 8 || die("too short file");

	$count = bigendian_to_num(substr($d, 0, 4));
	$tabler_child->add(sprintf("%012x", $pos), "Sample count: ", $count . " (" . sprintf("%08x", $count) . ")");
	$pos += 4;

	$delta = bigendian_to_num(substr($d, 4, 4));
	$tabler_child->add(sprintf("%012x", $pos), "Sample delta: ", $delta . " (" . sprintf("%08x", $delta) . ")");
	$pos += 4;
	
	if ( 0 ) {
	    $tabler_child->add(sprintf("", $pos), "First sample index: ", $sample);
	    $tabler_child->add(sprintf("", $pos), "First sample duration: ", $duration);
	}
	$sample += $count;
	$duration += $count * $delta;

	$tabler_child->add(sprintf("", $pos), "Total sample count: ", $sample);
	$tabler_child->add(sprintf("", $pos), "End time of last sample: ", $duration);

	$tabler_child->print;
	$fh->skip(8) == 8 || die("too short file");
	$len -= 8;
    }


    if ( $len > 0 ) {
	$fh->skip($len) == $len || die("too short file");
    }

    if ( $streamref->{mp4_sample_count} != $sample ) {
	die("sample size is vary!! stsz def: $streamref->{mp4_sample_count}, stts total: $sample");
    }

    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_ctts_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $sample;
    my $d;
    my $poscur;
    my $val;
    my $entrycount;
    my $i;
    my $samples;
    my $count;
    my $delta;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 4) == 4 || die("too short file");

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%08x", $entrycount) . ")");
    $pos += 4;
    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;

    $tabler->print;

    $sample = 0;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	$tabler->add(sprintf("%012x", $pos), sprintf("Entry[%d]:", $i));
	$tabler->print;

	if ( $len < 8 ) {
	    warn("too short size, $len");
	    goto error;
	}
	$fh->peek($d, 8) == 8 || die("too short file");

	$count = bigendian_to_num(substr($d, 0, 4));
	$tabler_child->add(sprintf("%012x", $pos), "Sample count: ", $count . " (" . sprintf("%08x", $count) . ")");
	$pos += 4;

	$delta = bigendian_to_num(substr($d, 4, 4));
	$tabler_child->add(sprintf("%012x", $pos), "Sample offset: ", $delta . " (" . sprintf("%08x", $delta) . ")");
	$pos += 4;
	
	if ( 0 ) {
	    $tabler_child->add(sprintf("", $pos), "First sample index: ", $sample);
	}
	$sample += $count;

	$tabler_child->add(sprintf("", $pos), "Total sample count: ", $sample);

	$tabler_child->print;
	$fh->skip(8) == 8 || die("too short file");
	$len -= 8;
    }


    if ( $len > 0 ) {
	$fh->skip($len) == $len || die("too short file");
    }

    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_stsc_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $entrycount;
    my $i;

    my $sizelist;

    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 4) == 4 || die("too short file");

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $pos += 4;
    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;

    $sizelist = "";
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	if ( $sizelist ne "" ) { $sizelist .= ";"; }

	if ( $len < 12 ) {
	    warn("too short size, $len");
	    goto error;
	}
	$fh->peek($d, 12) == 12 || die("too short file");

	$val = bigendian_to_num(substr($d, 0, 4));
	$sizelist .= $val;
	$sizelist .= ",";
	$pos += 4;

	$val = bigendian_to_num(substr($d, 4, 4));
	$sizelist .= $val;
	$sizelist .= ",";
	$pos += 4;

	$val = bigendian_to_num(substr($d, 8, 4));
	$sizelist .= $val;
	$pos += 4;

	$fh->skip(12) == 12 || die("too short file");
	$len -= 12;
    }


    if ( $len > 0 ) {
	$fh->skip($len) == $len || die("too short file");
    }

    $streamref->{mp4_stsc_list} = $sizelist;     
    
    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_stsc_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $entrycount;
    my $i;
    my $type;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    $fh->peek($d, 4) == 4 || die("too short file");

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%08x", $entrycount) . ")");
    $pos += 4;
    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;

    $tabler->print;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	$tabler->add(sprintf("%012x", $pos), sprintf("Entry[%d]:", $i));
	$tabler->print;

	if ( $len < 12 ) {
	    warn("too short size, $len");
	    goto error;
	}
	$fh->peek($d, 12) == 12 || die("too short file");

	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler_child->add(sprintf("%012x", $pos), "First chunk: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 4, 4));
	$tabler_child->add(sprintf("%012x", $pos), "Samples per chunk: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 8, 4));
	$tabler_child->add(sprintf("%012x", $pos), "Sample desc. index: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$tabler_child->print;
	$fh->skip(12) == 12 || die("too short file");
	$len -= 12;
    }


    if ( $len > 0 ) {
	$fh->skip($len) == $len || die("too short file");
    }

    return 1;

error:
    $fh->skip($len) == $len || die("too short file");
    return 0;
}

sub parse_stsz_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $sample_size;
    my $sample_count;
    my $total_size;
    my $i;

    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 12 ) {
	warn("too short size, $len");
	goto error;
    }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }

    $sample_size = bigendian_to_num(substr($d, 0, 4));
    $pos += 4;

    $sample_count = bigendian_to_num(substr($d, 4, 4));
    $pos += 4;

    if ( $fh->skip(8) != 8 ) { warn("too short file"); goto error; }
    $len -= 8;
    
    if ( $sample_size == 0 ) {
	my $sizelist = "";

	for ( $i=0; $i<$sample_count; $i++ ) {
	    if ( $len < 4 ) {
		warn("too short size, $len");
		goto error;
	    }
	    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
	    if ( $sizelist ne "" ) { $sizelist .= ","; }
	    	    
	    $val = bigendian_to_num(substr($d, 0, 4));
	    $sizelist .= $val;
	    $pos += 4;
	    
	    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
	    $len -= 4;
	}
	$streamref->{mp4_stsz_list} = $sizelist;	
    } else {
	$streamref->{mp4_default_sample_size} = $sample_size;	
    }

    $streamref->{mp4_sample_count} = $sample_count;
    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_stsz_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $sample_size;
    my $sample_count;
    my $entry_size;
    my $i;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);

    my @co_list;
    my @ts_list;
    my @sc_list;
    my @ts_entry;
    my @sc_entry;
    my $co_rest;
    my $ts_rest;
    my $co_index;
    my $ts_index;
    my $sc_index;
    my $cur_duration;
    my $cur_offset;
    my $next_offset;
    my $cur_sc_count;

    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 12 ) {
	warn("too short size, $len");
	goto error;
    }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }

    $sample_size = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Sample size: ", $sample_size . " (" . sprintf("%08x", $sample_size) . ")");
    $pos += 4;

    $sample_count = bigendian_to_num(substr($d, 4, 4));
    $tabler->add(sprintf("%012x", $pos), "Sample count: ", $sample_count . " (" . sprintf("%08x", $sample_count) . ")");
    $pos += 4;

    if ( $fh->skip(8) != 8 ) { warn("too short file"); goto error; }
    $len -= 8;

    $tabler->print;

    @co_list = split(/,/, $streamref->{mp4_chunk_offset_list});    
    @ts_list = split(/;/, $streamref->{mp4_stts_list});
    @sc_list = split(/;/, $streamref->{mp4_stsc_list});
    $co_rest = 0;
    $ts_rest = 0;
    $co_index = 0;
    $ts_index = 0;
    $sc_index = 0;
    $cur_sc_count = 0;
    $cur_duration = 0;
    $cur_offset = 0;
    $next_offset = 0;
    @ts_entry = split(/,/, $ts_list[$ts_index]);
    @sc_entry = split(/,/, $sc_list[$sc_index]);

    for ( $i=0; $i<$sample_count; $i++ ) {
	$tabler->add(" " x 12, "Sample[$i]:", "");
#	$tabler->print;
		     
	if ( $sample_size == 0 ) {    
	    if ( $len < 4 ) {
		warn("too short size, $len");
		goto error;
	    }
	    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
	    
	    $entry_size = bigendian_to_num(substr($d, 0, 4));
	    $tabler_child->add(sprintf("%012x", $pos), "Size: ", $entry_size . " (" . sprintf("%08x", $entry_size) . ")");
	    $pos += 4;
	
	    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
	    $len -= 4;
	} else {
	    $entry_size = $sample_size;
	}

	if ( $ts_rest == 0 ) {
	    @ts_entry = split(/,/, $ts_list[$ts_index]);
	    $ts_rest = $ts_entry[1];
	    $ts_index++;
	}

	if ( $co_rest == 0 ) {
	    $cur_offset = $co_list[$co_index];
	    $co_index++;
	    if ( $sc_entry[0] <= $co_index ) {
		$cur_sc_count = $sc_entry[1];
		$sc_index++;
		if ( $sc_index <= $#sc_list ) {
		    @sc_entry = split(/,/, $sc_list[$sc_index]);
		}
	    }
	    $co_rest = $cur_sc_count;
	}
	$co_rest--;
	$next_offset = $cur_offset + $entry_size;

#	printf("$sc_index / $#sc_list / $cur_sc_count / $co_index / $co_rest / $next_offset\n");
	
	$tabler_child->add(" " x 12, "Start offset: ", $cur_offset . " (" . sprintf("%012x", $cur_offset) . ")");
	$tabler_child->add(" " x 12, "End offset: ", $next_offset . " (" . sprintf("%012x", $next_offset) . ")");
	$tabler_child->add(" " x 12, "Decoding time count: ", $cur_duration);
	$tabler_child->add(" " x 12, "Decoding time: ", sprintf("%.3f", $cur_duration / $streamref->{mp4_duration_scale_mdhd}));
	
	$cur_offset = $next_offset;
	$cur_duration += $ts_entry[1];

#	$tabler_child->print;
    }

    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_stss_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $entrycount;
    my $d;
    my $poscur;
    my $val;
    my $sample_size;
    my $sample_count;
    my $i;
    my $type;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%08x", $entrycount) . ")");
    $pos += 4;

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	    
	if ( $len < 4 ) { warn("too short size, $len"); goto error; }
	if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
	    
	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler->add(sprintf("%012x", $pos), "Sample number[$i]: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
	
	if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
	$len -= 4;
    }

    $tabler->print;
    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_stco_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $entrycount;
    my $d;
    my $poscur;
    my $val;
    my $i;
    my $offsetlist;

    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) { warn("too short size, $len"); goto error; }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $pos += 4;

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;

    $offsetlist = "";
    for ( $i=0; $i<$entrycount; $i++ ) {
	    
	if ( $len < 4 ) { warn("too short size, $len"); goto error; }
	if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
	    
	$val = bigendian_to_num(substr($d, 0, 4));
	$pos += 4;

	if ( $offsetlist ne "" ) { $offsetlist .= ","; }
	$offsetlist .= $val;	
	if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
	$len -= 4;
    }

    $streamref->{mp4_chunk_offset_list} = $offsetlist;    
    $streamref->{mp4_chunk_count} = $entrycount;    
    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_stco_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $entrycount;
    my $d;
    my $poscur;
    my $val;
    my $sample_size;
    my $sample_count;
    my $i;
    my $type;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%08x", $entrycount) . ")");
    $pos += 4;

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	    
	if ( $len < 4 ) { warn("too short size, $len"); goto error; }
	if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
	    
	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler->add(sprintf("%012x", $pos), "Chunk offset[$i]: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pos += 4;
	
	if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
	$len -= 4;
    }

    $tabler->print;
    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_co64_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;
    my $entrycount;

    my $d;
    my $poscur;
    my $val;
    my $i;
    my $offsetlist;

    process_box_header($fh, $depth, $len, 1) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) { warn("too short size, $len"); goto error; }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $pos += 4;

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;

    $offsetlist = "";
    for ( $i=0; $i<$entrycount; $i++ ) {
	    
	if ( $len < 8 ) { warn("too short size, $len"); goto error; }
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	    
	$val = bigendian_to_num(substr($d, 0, 8));
	$pos += 8;

	if ( $offsetlist ne "" ) { $offsetlist .= ","; }
	$offsetlist .= $val;	
	if ( $fh->skip(8) != 8 ) { warn("too short file"); goto error; }
	$len -= 8;
    }

    $streamref->{mp4_chunk_offset_list} = $offsetlist;    
    $streamref->{mp4_chunk_count} = $entrycount;    
    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_co64_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $entrycount;
    my $d;
    my $poscur;
    my $val;
    my $sample_size;
    my $sample_count;
    my $i;
    my $type;
    my $tabler = Tabler->new($depth+1);
    my $tabler_child = Tabler->new($depth+2);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 8 ) {
	warn("too short size, $len");
	goto error;
    }
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");

    if ( $version > 0 ) {
	warn("too large version, $version");
	goto error;
    }

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    $pos = $fh->tell;
    
    if ( $fh->peek($d, 4) != 4 ) { warn("too short file"); goto error; }

    $entrycount = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Entry count: ", $entrycount . " (" . sprintf("%08x", $entrycount) . ")");
    $pos += 4;

    if ( $fh->skip(4) != 4 ) { warn("too short file"); goto error; }
    $len -= 4;
    
    for ( $i=0; $i<$entrycount; $i++ ) {
	    
	if ( $len < 8 ) { warn("too short size, $len"); goto error; }
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	    
	$val = bigendian_to_num(substr($d, 0, 8));
	$tabler->add(sprintf("%012x", $pos), "Chunk offset[$i]: ", $val . " (" . sprintf("%016x", $val) . ")");
	$pos += 8;
	
	if ( $fh->skip(8) != 8 ) { warn("too short file"); goto error; }
	$len -= 8;
    }

    $tabler->print;
    
    if ( $len > 0 ) {
	if ( $fh->skip($len) != $len ) { warn("too short file"); return 0; }
    }    
    return 1;

error:
    if ( $fh->skip($len) != $len ) { warn("too short file"); }
    return 0;
}

sub parse_mvhd_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;

    my $version;
    my $size;
    my $len;
    my $pos;

    my $d;
    my $poscur;
    my $val;
    my $duration;
    my $type;
    my $late_flag;
    my $tabler = Tabler->new($depth+1);


    process_box_header($fh, $depth, $len) || die("too short file");
    
    $pos = $fh->tell;
    if ( $len < 100 ) {
	warn("too short size, $len");
	goto error;
    }
    $fh->peek($d, 4) == 4 || die("too short file");
    $version = bigendian_to_num(substr($d, 0, 1));
    $tabler->add(sprintf("%012x", $pos), "Version: ", $version . " (" . sprintf("%02x", $version) . ")");

    $pos += 1;
    $val = bigendian_to_num(substr($d, 1, 3));
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%06x", $val) . ")");
    $late_flag = 0;

    if ( $version > 1 ) {
	warn("too large version, $version");
	goto error;
    }
    if ( $version == 1 ) {
	if ( $len < 112 ) {
	    warn("too short size, $len for version $version");
	    goto error;
	}
    }

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;
    $pos = $fh->tell;

    if ( $version == 1 ) {
	$fh->peek($d, 28) == 28 || die("too short file");
	
	$val = bigendian_to_num(substr($d, 0, 8));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%016x", $val) . ")");
	$pos += 8;

	$val = bigendian_to_num(substr($d, 8, 8));
	$tabler->add(sprintf("%012x", $pos), "Modification time: ", " (" . sprintf("%016x", $val) . ")");
	$pos += 8;

	$val = bigendian_to_num(substr($d, 16, 4));
	$tabler->add(sprintf("%012x", $pos), "Scale: ", $val . " (" . sprintf("%08x", $val) . ")");
	@{$inforef}{"mp4_duration_scale"} = $val;
	$pos += 4;

	$duration = bigendian_to_num(substr($d, 20, 8));
	@{$inforef}{"mp4_duration_count"} = $duration;

	if ( @{$inforef}{"mp4_duration_scale"} ) {
	    my $tabler_child = Tabler->new($depth+2);
	    
	    $val = $duration / @{$inforef}{"mp4_duration_scale"};
	    $tabler_child->add(" " x 12, "Duration time: ", sprintf("%0.3f", $val));
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $val . " (" . sprintf("%016x", $val) . ")", $tabler_child);
	    @{$inforef}{"duration_time"}=$val;
	} else {
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $val . " (" . sprintf("%016x", $val) . ")");
	}
	$pos += 8;

	$fh->skip(28) == 28 || die("too short file");
	$len -= 28;
    } else {
	# version is zero

	$fh->peek($d, 16) == 16 || die("too short file");
	
	$val = bigendian_to_num(substr($d, 0, 4));
	$tabler->add(sprintf("%012x", $pos), "Creation time: ", " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 4, 4));
	$tabler->add(sprintf("%012x", $pos), "Modification time: ", " (" . sprintf("%08x", $val) . ")");
	$pos += 4;

	$val = bigendian_to_num(substr($d, 8, 4));
	$tabler->add(sprintf("%012x", $pos), "Scale: ", $val . " (" . sprintf("%08x", $val) . ")");
	@{$inforef}{"mp4_duration_scale"}=$val;
	$pos += 4;

	$duration = bigendian_to_num(substr($d, 12, 4));
	@{$inforef}{"mp4_duration_count"} = $duration;
	
	if ( @{$inforef}{"mp4_duration_scale"} ) {
	    my $tabler_child = Tabler->new($depth+2);
	    
	    $val = $duration / @{$inforef}{"mp4_duration_scale"};
	    $tabler_child->add(" " x 12, "Duration time: ", sprintf("%0.3f", $val));
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%08x", $duration) . ")", $tabler_child);
	    @{$inforef}{"duration_time"}=$val;
	} else {
	    $tabler->add(sprintf("%012x", $pos), "Duration: ", $duration . " (" . sprintf("%08x", $duration) . ")");
	}
	$pos += 4;

	$fh->skip(16) == 16 || die("too short file");
	$len -= 16;
    }
    $pos = $fh->tell;
    $fh->peek($d, 6) == 6 || die("too short file");
	
    $val = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Rate: ", sprintf("%0.3f", $val / 65536.0) . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $val = bigendian_to_num(substr($d, 4, 2));
    $tabler->add(sprintf("%012x", $pos), "Volume: ", sprintf("%0.3f", $val / 256.0) . " (" . sprintf("%04x", $val) . ")");
    $pos += 2;

    $fh->skip(16) == 16 || die("too short file");
    $len -= 16;

    $fh->skip(36) == 36 || die("too short file");
    $len -= 36;

    $fh->skip(24) == 24 || die("too short file");
    $len -= 24;

    $pos = $fh->tell;
    $fh->peek($d, 4) == 4 || die("too short file");
	
    $val = bigendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Next track ID: ", $val . " (" . sprintf("%08x", $val) . ")");
    $pos += 4;

    $fh->skip(4) == 4 || die("too short file");
    $len -= 4;

    $pos = $fh->tell;
    $fh->peek($d, 64) == 64 || die("too short file");
    $fh->skip($len) == $len || die("too short file");
    $tabler->print;

    return 1;
}

sub parse_stbl_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;

    process_box_header($fh, $depth, $len, 1) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "stsd" ) {
#	    $fh->skip($size) == $size || last;
	    parse_stsd_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stts" ) {
#	    $fh->skip($size) == $size || last;
	    parse_stts_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "ctts" ) {
	    $fh->skip($size) == $size || last;
#	    parse_ctts_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stsc" ) {
#	    $fh->skip($size) == $size || last;
	    parse_stsc_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stsz" ) {
#	    $fh->skip($size) == $size || last;
	    parse_stsz_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "sts2" ) {
	    $fh->skip($size) == $size || last;
#	    parse_sts2_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stco" ) {
#	    $fh->skip($size) == $size || last;
	    parse_stco_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "co64" ) {
#	    $fh->skip($size) == $size || last;
	    parse_co64_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stss" ) {
	    $fh->skip($size) == $size || last;
#	    parse_stss_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    $fh->skip($size) == $size || last;
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    $streamref->{mp4_had_stbl_box} = 1;
    return 1;

  error:
    return undef;
}

sub parse_stbl_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;

    process_box_header($fh, $depth, $len) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "stsd" ) {
#	    skip_box($fh, $depth+1);
	    parse_stsd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stts" ) {
#	    skip_box($fh, $depth+1);
	    parse_stts_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "ctts" ) {
	    skip_box($fh, $depth+1);
#	    parse_ctts_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stsc" ) {
#	    skip_box($fh, $depth+1);
	    parse_stsc_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stsz" ) {
#	    skip_box($fh, $depth+1);
	    parse_stsz_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "sts2" ) {
	    skip_box($fh, $depth+1);
#	    parse_sts2_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stco" ) {
#	    skip_box($fh, $depth+1);
	    parse_stco_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "co64" ) {
#	    skip_box($fh, $depth+1);
	    parse_co64_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stss" ) {
#	    skip_box($fh, $depth+1);
	    parse_stss_box($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    skip_box($fh, $depth+1);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    return 1;

  error:
    return undef;
}

sub parse_minf_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;

    process_box_header($fh, $depth, $len, 1) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "vmhd" ) {
	    $fh->skip($size) == $size || last;
#	    parse_vmhd_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "smhd" ) {
	    $fh->skip($size) == $size || last;
#	    parse_smhd_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "hmhd" ) {
	    $fh->skip($size) == $size || last;
#	    parse_hmhd_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "nmhd" ) {
	    $fh->skip($size) == $size || last;
#	    parse_nmhd_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "dinf" ) {
	    $fh->skip($size) == $size || last;
#	    parse_dinf_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stbl" ) {
#	    $fh->skip($size) == $size || last;
	    parse_stbl_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    $fh->skip($size) == $size || last;
#	    skip_box($fh, $depth+1);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    $streamref->{mp4_had_minf_box} = 1;
    return 1;

  error:
    return undef;
}

sub parse_minf_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;

    process_box_header($fh, $depth, $len) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "vmhd" ) {
	    skip_box($fh, $depth+1);
#	    parse_vmhd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "smhd" ) {
	    skip_box($fh, $depth+1);
#	    parse_smhd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "hmhd" ) {
	    skip_box($fh, $depth+1);
#	    parse_hmhd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "nmhd" ) {
	    skip_box($fh, $depth+1);
#	    parse_nmhd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "dinf" ) {
	    skip_box($fh, $depth+1);
#	    parse_dinf_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "stbl" ) {
#	    skip_box($fh, $depth+1);
	    parse_stbl_box($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    skip_box($fh, $depth+1);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    return 1;

  error:
    return undef;
}


sub parse_mdia_box_first {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;

    process_box_header($fh, $depth, $len, 1) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "mdhd" ) {
#	    $fh->skip($size) == $size || last;
	    parse_mdhd_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "hdlr" ) {
#	    $fh->skip($size) == $size || last;
	    parse_hdlr_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "minf" ) {
#	    $fh->skip($size) == $size || last;
	    parse_minf_box_first($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    $fh->skip($size) == $size || last;
#	    skip_box($fh, $depth+1);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    $streamref->{mp4_had_mdia_box} = 1;
    return 1;

  error:
    return undef;
}

sub parse_mdia_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $streamref = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;

    process_box_header($fh, $depth, $len) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	if ( $fh->peek($d, 8) != 8 ) { warn("too short file"); goto error; }
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "mdhd" ) {
#	    skip_box($fh, $depth+1);
	    parse_mdhd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "hdlr" ) {
#	    skip_box($fh, $depth+1);
	    parse_hdlr_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "minf" ) {
#	    skip_box($fh, $depth+1);
	    parse_minf_box($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    skip_box($fh, $depth+1);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    return 1;

  error:
    return undef;
}

sub parse_trak_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $size;
    my $len;
    my $len2;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $my_fho;
    my $my_fh = new Peekable;
    my $my_d;
    my $streamref = {};
    my $d;

    process_box_header($fh, $depth, $len) || die("too short file");
    $fh->peek($my_d, $len) == $len || die("too short file, len is $len");
    open $my_fho, '<', \$my_d or die $!;
    $my_fh->set_fd($my_fho);

    $len2 = $len;
    while ( $len2 > 0 ) {
	if ( $len2 < 8 ) {
	    die("too short child size, $d", $len2);
	}
	$pos = $my_fh->tell;
	$my_fh->peek($d, 8) == 8 || die("too short file");
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "tkhd" ) {
	    parse_tkhd_box_first($my_fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "mdia" ) {
	    parse_mdia_box_first($my_fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    $my_fh->skip($size) == $size || last;
	}	
	$poscur = $my_fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len2 -= $size;
    }
    $my_fh->close;
    
    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	$fh->peek($d, 8) == 8 || die("too short file");
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "tkhd" ) {
	    parse_tkhd_box($fh, $depth+1, $inforef, $streamref) || die;
	} elsif ( $type eq "mdia" ) {
	    parse_mdia_box($fh, $depth+1, $inforef, $streamref) || die;
	} else {
	    $fh->skip($size);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }
    
    push(@{$inforef->{streams}}, $streamref);

    return 1;
}


sub parse_moov_box {
    my $fh = shift;
    my $depth = shift;
    my $inforef = shift;
    my $size;
    my $len;
    my $pos;
    my $poscur;
    my $rval;
    my $type;
    my $d;
    
    process_box_header($fh, $depth, $len) || die("too short file");

    while ( $len > 0 ) {
	if ( $len < 8 ) {
	    die("too short child size, $d", $len);
	}
	$pos = $fh->tell;
	$fh->peek($d, 8) == 8 || die("too short file");
	$size = bigendian_to_num(substr($d, 0, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "mvhd" ) {
#	    skip_box($fh, $depth+1);
	    parse_mvhd_box($fh, $depth+1, $inforef) || die;
	} elsif ( $type eq "trak" ) {
#	    skip_box($fh, $depth+1);
	    parse_trak_box($fh, $depth+1, $inforef) || die;
	} else {
	    skip_box($fh, $depth+1);	    
	}	
	$poscur = $fh->tell;
	if ( $poscur - $pos != $size ) {
	    die("length mismatch $poscur - $pos != $size");
	}
	$len -= $size;
    }

    return 1;
}

sub parse_mp4_file {
    my $fh = shift;
    my $d = "";
    my $type;
    my $len = 0;
    my $inforef = { streams=>[] };

    while ( 1 ) {
	if ( $fh->peek($d, 8) != 8 ) {
	    print("end of stream\n");
	    last;
	}

	$len = bigendian_to_num(substr($d, 4));
	$type = substr($d, 4, 4);

	if ( $type eq "ftyp" ) {
	    parse_ftyp_box($fh, 0); 
	} elsif ( $type eq "mdat") {
	    skip_box($fh, 0);
	} elsif ( $type eq "moov" ) {
	    parse_moov_box($fh, 0, $inforef);
	} else {
	    skip_box($fh, 0);
	}	    
	$d = "";
    }


    foreach my $i ( keys %{$inforef} ) {
	print $i, ": ", $inforef->{$i}, "\n";
    }
    my $streams = $inforef->{streams};

    foreach my $i ( @{$streams} ) {
	print $i, "\n";
	foreach my $j ( keys %{$i} ) {
	    print $i, "->", $j, ": ", $i->{$j}, "\n";
	}
    }

    return $inforef;
}


sub get_cur_sample_offset {
    my $ref = shift;
    
    if ( $ref->{cur_sample_in_chunk} == 0 ) {
	my @chunk_offsets;
	@chunk_offsets = split /,/,$ref->{stream_ref}->{mp4_chunk_offset_list};

	$_[0] = $chunk_offsets[$ref->{cur_chunk}];
	return 1;
    } else {
	$_[0] = $ref->{cur_offset};
	return 1;
    }
    
    
    return 0;
}

sub get_cur_sample_dts {
    my $ref = shift;
    my @stts_list;
    my @stts_cur;
    my $top_sample;
    my $last_sample;
    my $top_tick;

    $top_sample = 0;
    $top_tick = 0;
    @stts_list = split /;/,$ref->{stream_ref}->{mp4_stts_list};
    
    while ( my @stts_cur = split /,/,shift(@stts_list) ) {	
	$last_sample = $top_sample + $stts_cur[0];

	if ( $last_sample > $ref->{cur_sample} ) {
	    $_[0] = $top_tick + $stts_cur[1] * ( $ref->{cur_sample} - $top_sample );
	    return 1;
	}
	$top_tick = $top_tick + $stts_cur[0] * $stts_cur[1];
	$top_sample = $last_sample;
    }

    return 0;
}

sub take_next_sample {
    my $ref = shift;
    my @stts_list;
    my @stts_cur;
    my $top_sample;
    my $last_sample;
    my $top_tick;

    $top_sample = 0;
    $top_tick = 0;
    @stts_list = split /;/,$ref->{stream_ref}->{mp4_stts_list};
    
    while ( my @stts_cur = split /,/,shift(@stts_list) ) {	
	$last_sample = $top_sample + $stts_cur[0];

	if ( $last_sample > $ref->{cur_sample} ) {
	    $_[0] = $top_tick + $stts_cur[1] * ( $ref->{cur_sample} - $top_sample );
	    return 1;
	}
	$top_tick = $top_tick + $stts_cur[0] * $stts_cur[1];
	$top_sample = $last_sample;
    }

    return 0;
}

sub sort_chunks_by_offset {
    my $inforef = shift;
    my @stream_infos; 

    if ( ! $inforef ) {
	return 0;
    }

    foreach my $r ( @{$inforef->{streams}} ) {
	my $localref = {
	    stream_ref => $r,
	    cur_chunk => 0,
	    cur_sample => 0,
	    cur_sample_in_chunk => 0,
	    cur_offset => 0,
	};
	
	push(@stream_infos, $localref );
    }

    INF_LOOP: while ( 1 ) {
	my $cont = 0;
	my $cur_time;
	my $max_time;
	my $max_time_stream;
	my $max_time_valid;
	my $min_time;
	my $min_time_stream;
	my $min_time_valid;
	my $cur_offset;
	my $max_offset;
	my $max_offset_stream;
	my $max_offset_valid;
	my $min_offset;
	my $min_offset_stream;
	my $min_offset_valid;

	CONT_CHECK: foreach my $r ( @stream_infos ) {
	    print $r, qq( ), $r->{stream_ref}->{mp4_chunk_count}, qq( ), $r->{cur_chunk}, qq(\n);

	    if ( $r->{stream_ref}->{mp4_chunk_count} > $r->{cur_chunk} ) {
		$cont = 1;
		last CONT_CHECK;
	    }
	}
	last INF_LOOP unless $cont;

	$max_time_valid = 0;
	$min_time_valid = 0;
	$max_offset_valid = 0;
	$min_offset_valid = 0;

	GET_NEXT: for ( my $i = 0; $i <= $#stream_infos; $i++ )  {
	    my $cur_r = $stream_infos[$i];

	    if ( get_cur_sample_offset($cur_r, $cur_offset) ) {
		if ( !$max_offset_valid or $max_offset < $cur_offset ) {
		    $max_offset = $cur_offset;
		    $max_offset_stream = $i;
		    $max_offset_valid = 1;
		} 
		if ( !$min_offset_valid or $min_offset > $cur_offset ) {
		    $min_offset = $cur_offset;
		    $min_offset_stream = $i;
		    $min_offset_valid = 1;
		} 
		print $i, qq( ), $cur_offset, qq(\n);
	    }

	    if ( get_cur_sample_dts($cur_r, $cur_time) ) {
		if ( !$max_time_valid or $max_time < $cur_time ) {
		    $max_time = $cur_time;
		    $max_time_stream = $i;
		    $max_time_valid = 1;
		} 
		if ( !$min_time_valid or $min_time > $cur_time ) {
		    $min_time = $cur_time;
		    $min_time_stream = $i;
		    $min_time_valid = 1;
		} 
		print $i, qq( ), $cur_time, qq(\n);
	    }
	}
	print $max_time, qq( ), $max_time_stream, qq(\n);
	print $min_time, qq( ), $min_time_stream, qq(\n);
	print $max_offset, qq( ), $max_offset_stream, qq(\n);
	print $min_offset, qq( ), $min_offset_stream, qq(\n);
    
	print "aaa",qq(\n);

	last INF_LOOP;
    };

    return 1;
}

{
    my $fho = new FileHandle;
    my $fh = new Peekable;
    my $d;
    
  ONEFILE: for (@ARGV) {
      print "----\n";
      print $_, "\n\n";
      
      open($fho, $_) || (print("cannot be open.\n"), next ONEFILE);

#      print tell($fho);
      $fh->set_fd($fho);
      
      $fh->peek($d, 8) || die("too short file\n");
      
      if ( substr($d, 4, 4) ne "ftyp" ) {
	  print "NOT ASF FILE!!\n"; 
      } else {
	  my $ref;

	  $ref = parse_mp4_file($fh);
	  if ( $ref ) {
	      sort_chunks_by_offset($ref);
	  }
      }
      
      
      $fh->close;
  }
}
